# 第4周作业：通过Python编程完成一个银行ATM机模拟系统，具备如下功能:
# (1)登陆验证：用户输入用户名密码登陆，检测用户名是否存在以及用户名密码是否匹配；用户名密码共有三次输入机会，超过三次系统退出。
# (2)菜单界面：登陆成功后显示功能操作界面，输入序号选择对应功能。
# (3)用户注册：用户可以输入用户名和密码创建自己的账号，并输入电话号码等信息，如果用户名存在则让用户重新输入用户名。注册后免费赠送5000元余额。
# (4)账户管理：用户可以随时查看自己的账户余额。用户可以输入其他账户用户名，实现转账功能；转账时用户名必须存在，否则提示异常。用户也可以模拟实现存取款功能。
# (5)用户名和密码以及账户信息等必须永久保存。且基于命令行完成，不需要开发GUI界面。
# ps:实现方式不限
# 第5点 这个，不需要强制完成，有能力的话，可以考虑下如何去做，提示下 open 语句或者json的dump

import logging
import bcrypt
from sqlalchemy import create_engine, Integer, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
import sqlalchemy

DATAFMT = "%Y/%m/%d-%H:%M:%S"
FORMAT = "%(lineno)d %(asctime)s %(processName)s %(process)d %(threadName)s %(thread)d   %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATAFMT, filename="error.log")
USERNAME = "xhm"
PASSWORD = "159180"
HOST = "192.168.204.128"
PORT = 3306
DBNAME = "User"
engine = create_engine(f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}")  # , echo=True)
Base = declarative_base()


class User(Base):
    __tablename__ = "User"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(15), nullable=False, unique=True)
    password = Column(String(128), nullable=False)
    money = Column(Integer)
    phone_number = Column(Integer)

    def __repr__(self):
        return "<{} {} {} {} {}>".format(
            __class__.__name__,
            self.id,
            self.name,
            self.money,
            self.phone_number,
        )


class Atm:
    PRESENT_MONEY = 5000
    TIMES = 3

    def __init__(self, name="NB_ATM", session: Session = None):
        self.name = name
        self.session: sqlalchemy.orm.Session = session
        self.func = {
            '1': self._login,
            '2': self._register,
        }
        self.sub_func = {
            '0': lambda x: 0,
            '1': __class__.show_money,
            '2': self.remit_money,
            '3': self.save_money,
        }

    def _register(self):
        """用户注册方法"""
        name = input("请输入你的用户名---> ")
        query = self.query(name)
        if query:
            logging.info("此用户名已存在，请更改")
            return
        try:
            password = input("请输入密码---> ").strip()
            if __class__.password_strength_detection(password):
                phone_num = input("请输入手机号---> ").strip()
                ps = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode()
                user = User()
                user.name = name
                user.password = ps
                user.money = self.PRESENT_MONEY
                user.phone_number = phone_num
                self.session.add(user)
                self.session.commit()
                logging.info(f"注册成功，您的用户名为:{name}")

        except Exception as e:
            logging.error(e)

    def _login(self):
        """用户登录方法，有三次登录机会，失败就退出，没采取锁死用户账户的策略"""
        name = input("请输入你的用户名---> ").strip()
        user = self.query(name)
        if user:
            for _ in range(self.TIMES):
                if __class__.check_password(user):  # OK
                    logging.info("登陆成功")
                    self.login_ok(user)
                    break
        else:
            logging.info("不存在此用户")

    def _admin(self):
        """后台管理，未实现"""
        pass

    def show(self):
        """基本页面，公共功能页面，可以进行功能选择及路由"""
        while True:
            logging.info('''
           **********************
           *  1.登陆   2.开户    *
           ------以下未实现-------
           *  3.查询   4.取款    *
           *  5.存款   6.转账    *
           *  7 注销   0.退出    *
           **********************
           ''')
            try:
                cmd = input('>>>').strip()
                self.func.get(cmd, lambda: logging.info("不存在此选项"))()
            except Exception as e:
                logging.error(e)

    def query(self, name):
        """通过用户名进行查询，如果成功返回那个用户的记录，否则返回None"""
        user = self.session.query(User).filter(User.name == name)
        return user.first()

    @classmethod
    def check_password(cls, user):
        """密码校验方法，校验成功返回True"""
        password = input("你的密码为---> ").strip().encode()
        if bcrypt.checkpw(password, user.password.encode()):
            return True

    def login_ok(self, user):
        """登陆成功的功能页面，实现二级功能的再路由"""
        while True:
            logging.info('''
           ********个人页面***********
           *   0.登出    1.余额      *
           *   2.转账    3.存款      *
           **************************
           ''')
            cmd = input('>>>').strip()
            if cmd == '0':
                break
            self.sub_func.get(cmd, lambda x: logging.info("不存在此选项"))(user)

    @classmethod
    def show_money(cls, user):
        """显示传入用户的余额信息"""
        logging.info(f"你的余额为---> {user.money}元")
        return user.money

    def remit_money(self, user):
        """转账方法，需要知道对方用户名"""
        name = input("请输入他/她的名字")
        if name == user.name:
            logging.info("你不能给自己打钱")
            return
        r_user = self.query(name)
        if r_user:
            old_money = __class__.show_money(user)
            try:
                money = float(input("请输入转账金额---> ").strip())
                if old_money < money:
                    logging.info("你钱没那么多")
                else:
                    r_user.money += money
                    user.money -= money
                    self.session.add(r_user)
                    self.session.add(user)
                    self.session.commit()
            except Exception as e:
                logging.error(e)
                logging.info("请你输入要转账的数目")

            __class__.show_money(user)
        else:
            logging.info("没有这个人")

    def save_money(self, user):
        """存钱方法"""
        money = int(input("请输入存款金额").strip())
        if money > 0:
            user.money += money
            self.session.add(user)
            self.session.commit()
        else:
            logging.info("存款金额错误")
        self.show_money(user)

    @classmethod
    def password_strength_detection(cls, password):
        """密码强度检测，未实现"""
        return password


if __name__ == '__main__':
    mysession = sessionmaker(bind=engine)()
    atm = Atm(session=mysession)
    atm.show()


# 既然都用logging了，那么print是不是考虑去掉？
